package training.framework.presentation;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import training.framework.business.AbstractEntity;
import training.framework.business.AbstractEntitySearchOptions;


public abstract class AbstractEntityDao<Identifier, Entity extends AbstractEntity<Identifier>, EntitySearchOptions extends AbstractEntitySearchOptions> {
	
	private EntityManagerFactory factory;
	private Class<Entity> entityClass;
	
	// -- CONSTRUTOR
	
	public AbstractEntityDao(EntityManagerFactory factory, Class<Entity> entityClass){
		if(factory == null){
			throw new IllegalArgumentException("Factory can not be null");
		}
		if(entityClass == null){
			throw new IllegalArgumentException("Entity Class can not be null");
		}
		
		this.factory = factory;
		this.entityClass = entityClass;
	}
	
	public int selectEntityCount(EntitySearchOptions options) {		
		validateOptions(options);
		EntityManager manager = null;
		
		try {
			manager =  this.factory.createEntityManager();

			CriteriaBuilder builder = manager.getCriteriaBuilder();
			CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
			
			Root<Entity> root = criteriaQuery.from(entityClass);
			
			criteriaQuery.select(builder.count(root));
			criteriaQuery.where(toPredicate(builder, root, options));
			
			
			TypedQuery<Long> query = manager.createQuery(criteriaQuery);
			
			Long result = query.getSingleResult();
			return result.intValue();
		} finally {
			if (manager != null) {
				try {
					manager.close();
				} catch (RuntimeException e) {}
			}
		}						
	}
	
	public List<Entity> selectEntity(EntitySearchOptions options) {
		validateOptions(options);
		EntityManager manager = null;
		try {
			manager =  this.factory.createEntityManager();

			CriteriaBuilder builder = manager.getCriteriaBuilder();
			CriteriaQuery<Entity> criteriaQuery = builder.createQuery(entityClass);
			Root<Entity> root = criteriaQuery.from(entityClass);
						
			criteriaQuery.select(root);
			criteriaQuery.where(toPredicate(builder, root, options));
			
			Order order = toOrder(builder,root);
			
			if(order != null)
				criteriaQuery.orderBy(order);
			
			TypedQuery<Entity> query = manager.createQuery(criteriaQuery);
			if (options.getFirstResult() != null) {
				query.setFirstResult(options.getFirstResult());
			}
			if (options.getMaxResults() != null) {
				query.setMaxResults(options.getMaxResults());
			}
			
			List<Entity> result = query.getResultList();
			return result;
		} finally {
			if (manager != null) {				
				try {
					manager.close();
				} catch (RuntimeException e) {}
			}
		}				
	}	
	
	protected abstract Order toOrder(CriteriaBuilder builder, Root<Entity> root);

	public void insertEntity(final Entity entity) {
		validateEntity(entity);
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				manager.persist(entity);
			}
		};
		template.execute(factory);
	}	
	
	protected abstract Predicate toPredicate(CriteriaBuilder builder, Root<Entity> root, EntitySearchOptions options);
	
	protected String toLikeParameter(String parameter) {
		return "%" + parameter.toLowerCase().trim() + "%";
	}

	public void updateEntity(final Entity entity) {
		validateEntity(entity);
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				manager.merge(entity);
			}
		};
		template.execute(factory);
	}
	
	public void deleteEntity(final Entity entity) {
		validateEntity(entity);
		
		if(entity.getIdentifier() == null){
			throw new IllegalArgumentException("entity.getIdentifier() == null");
		}
		
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				Entity databaseEntity = manager.find(entityClass, entity.getIdentifier());
				manager.remove(databaseEntity);
			}
		};
		template.execute(factory);
	}
	
	public Entity selectEntity(Identifier identifier){
		if(identifier == null){
			throw new IllegalArgumentException("identifier == null");
		}
		
		EntityManager manager = null;
		try{
			manager = factory.createEntityManager();
			return manager.find(entityClass, identifier);
			
		}finally{
			if(manager != null){
				try{
					manager.clear();
				}catch(RuntimeException e){}
			}
		}
	}
	
	private void validateEntity(Entity entity){
		if(entity == null){
			throw new IllegalArgumentException("entity == null");
		}
	}
	
	private void validateOptions(EntitySearchOptions options){
		if(options == null){
			throw new IllegalArgumentException("options == null");
		}
	}
}
